#!/usr/bin/env python
import os,sys,pprint,optparse,shelve,copy
import urlparse,csv,difflib,time,datetime,sets
import math
import wx


############################################################################
#---------------------------------------------------------------------------
# Begin Netview code
#---------------------------------------------------------------------------
############################################################################

class EventSlice:
    def __init__(self,rawSlice,timeline):
        self.rawSlice = rawSlice
        self.timeline = timeline
    def __getitem__(self,key):
        return self.rawSlice[key]
    def GetStart(self):
        return self.rawSlice['startTime']
    def GetEnd(self):
        return self.rawSlice['endTime']
    def GetEvent(self,index):
        event = self.rawSlice['events'][index]
        return event

class PenRegDict(dict):
    def __init__(self,line=None):
        if line is not None:
            self.fromLine(line)
    def setNode1IP(self,ip):
        self['node1']['ip'] = ip
    def setNode2IP(self,ip):
        self['node2']['ip'] = ip
    def getIpString(self,which):
        return '.'.join(map(str,self[which]['ip']))
    ports = None
    def _prepPorts(self):
        if self.ports is None:
            p1 = self['node1']['logical'].strip()
            p1 = int(p1) if p1 else 'icmp'
            p2 = self['node2']['logical'].strip()
            p2 = int(p2) if p2 else 'icmp'
            self.ports = [p1,p2]
    def hasPort(self,port):
        self._prepPorts()
        return port in self.ports
    def getPort(self,which):
        self._prepPorts()
        return self.ports[which]
    def fromLine(self,line):
        self['type'] = line[0].lower()
        attrs = ['name','ip','port','logical','physical','bytes','packets']
        index=1
        self['node1'] = {}
        self['node2'] = {}
        for attr in attrs:
            self['node1'][attr] = line[index]
            index += 1
        for attr in attrs:
            self['node2'][attr] = line[index]
            index += 1
        for node in ('node1','node2'):
            self[node]['ip'] = map(int,self[node]['ip'].split('.'))
        self['totalBytes'] = line[15]
        self['totalPackets'] = line[16]
        self['duration'] = line[17]
        self['start'] = line[18]
        self['stop'] = line[19]
        self['startGMT'] = line[20]
        self['stopGMT'] = line[21]

        for tstype in ('start','stop','startGMT','stopGMT'):
            self[tstype],msec = self[tstype].split('.')
            strptime = datetime.datetime.strptime
            self[tstype] = strptime(self[tstype],'%Y/%m/%d %H:%M:%S')
            msec = int(float('.'+msec[:-1])*1000000)
            self[tstype] = self[tstype].replace(microsecond=msec)
    typeMap = {'udp':0,'icmp':1,'tcp':2,
               0:'udp',1:'icmp',2:'tcp'}

    def storeCopy(self):
        n1,n2 = self['node1'],self['node2']
        rlist = [
            [n1['port'],n1['logical'],n1['bytes'],n1['packets']],
            [n2['port'],n2['logical'],n2['bytes'],n2['packets']],
            self['totalBytes'],self['totalPackets'],self['duration'],
            self['startGMT'],self['stopGMT']
            ]
        if self['type'] in ('udp','icmp'):
            rlist[0].append(PenRegDict.typeMap[self['type']])
        return rlist

    def fromStoreCopy(self,storeCopy):
        if len(storeCopy[0])==5:
            ((n1p,n1l,n1b,n1pk,type),(n2p,n2l,n2b,n2pk),
             tb,tp,dur,str,stp) = storeCopy
            type = PenRegDict.typeMap[type]
        else:
            ((n1p,n1l,n1b,n1pk),(n2p,n2l,n2b,n2pk),
             tb,tp,dur,str,stp) = storeCopy
            type = 'tcp'
        self['type'] = type
        self['node1'],self['node2'] = n1,n2 = {},{}
        (n1['port'],n1['logical'],
         n1['bytes'],n1['packets']) = n1p,n1l,n1b,n1pk
        (n2['port'],n2['logical'],
         n2['bytes'],n2['packets']) = n2p,n2l,n2b,n2pk
        (self['totalBytes'],self['totalPackets'],
         self['duration'],self['startGMT'],
         self['stopGMT']) = tb,tp,dur,str,stp
        
        
###################################################################
# Sorting Methods for various incarnations of the PenRegDict
###################################################################

def sort_connections(x,y):
    dict1,dict2 = x[2],y[2]
    return cmp(dict1['startGMT'],dict2['startGMT'])

def sort_events(x,y):
    if ('guiInfo' in x) and ('guiInfo' in y):
        xInfo,yInfo = x['guiInfo'],y['guiInfo']
        if ('index' in xInfo) and ('index' in yInfo):
            if xInfo['index']>yInfo['index']: return 1
            if xInfo['index']<yInfo['index']: return -1
            return 0
    if x['startGMT']>y['startGMT']: return 1
    if x['startGMT']<y['startGMT']: return -1
    return 0

def sort_connections_tuples(x,y):
    # sort connections list (list of tuples) by start time
    tup1,tup2 = x[2],y[2]
    c = cmp(tup1[-2],tup2[-2])
    if not c:
        # if start times are the same, sort by duration
        c = cmp(tup1[-3],tup2[-3])
        if not c:
            # if durations are the same, sort by total packets
            c = cmp(tup1[-4],tup2[-4])
    return c


###################################################################
#   MetadataFile
#
#   Keeps track of:
#      - timeslice-to-filename relationships
#      - reads from text file with python code in it, specifically a
#        single pretty-printed dictionary with all the relevant data
#
#        !!!!!!!!!!!! ACTUNG !!!!!!!!!!!!
#      - uses eval (EXTREMELY UNSAFE!)
#        - possible fix:
#         http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496746
###################################################################
class MetadataFile(dict):
    defaultName = '.metadata'
    defaultDict = { 'version':0.02,
                    'files': {},
                    'baseNames': {},
                    'settings': {},
                    }
    def __init__(self,directory,name=None):
        filePath = os.path.join(directory,name or MetadataFile.defaultName)
        self.parse(filePath)
    def _setupDefaultInfo(self):
        # create default metadata file
        for key,value in MetadataFile.defaultDict.items():
            self[key] = copy.deepcopy(value)
        self.sync()
    def getSettings(self):
        return self['settings']
    def setSetting(self,key,value):
        self['settings'][key] = value
    def getCurrentVersion(self):
        return MetadataFile.defaultDict['version']
    def parse(self,filePath):
        # !!!!! EXTREMELY UNSAFE !!!!!!!!
        # makes a LOT of assumptions, primarily that the metadata file
        # is of the correct format, but most importantly that what
        # it's eval-ing is not malicious
        self.filePath = filePath
        print 'Reading metadata file at',filePath
        if not os.path.exists(filePath):
            self._setupDefaultInfo()
        else:
            f = open(filePath)
            info = eval(f.read())
            f.close()
            if (('version' not in info) or
                info['version'] < self.getCurrentVersion()):
                self._setupDefaultInfo()
            else:
                for name in info.keys():
                    self[name] = info[name]
    def sync(self):
        f = open(self.filePath,'w')
        self['syncTime'] = time.time()
        f.write(pprint.pformat(self))
        f.close()

###################################################################
# Workspace
#
# Sets up a workspace directory
#
# The workspace should be a directory where the created shelve files
#   can be placed and parsed later on
#
# Workspace directory should have a '.metadata' file in it (see above)
###################################################################
class Workspace:
    defaultName = '.netview'
    metadata = None
    def __init__(self,directory=None,metadataFileName=None):
        if directory is None:
            # default behavior is to set up new workspace from
            # wherever the user runs the script
            curDir = os.path.abspath('.')
            directory = os.path.join(curDir,Workspace.defaultName)
        if not os.path.exists(directory):
            os.makedirs(directory)
        self.directoryPath = directory
        if metadataFileName is not None:
            self.metadata = MetadataFile(self.directoryPath,
                                         metadataFileName)
        self.metadata = self.getMetadata()
    def getMetadata(self):
        if self.metadata is None:
            self.metadata = MetadataFile(self.directoryPath)
        return self.metadata
    def getShelveFileName(self,fileName):
        metadata = self.getMetadata()
        absFileName = os.path.abspath(fileName)
        noExtBase = os.path.splitext(fileName)[0]
        baseFileName = os.path.split(noExtBase)[1]
        if not baseFileName in metadata['baseNames']:
            # this is the first instance of this file for this
            # metadata file
            metadata['baseNames'][baseFileName] = 0
        else:
            # it's been added before
            if not self.hasFile(fileName):
                # don't have a hashing for this file's absolute path
                # (i.e. it hasn't been added to the metadata file, but
                # another file with the same base-name (but different
                # path) has been added... So, give this shelve file a
                # different name, but hash it to its original absolute
                # path
                num = metadata['baseNames'][baseFileName]
                baseFileName += '.'+str(num)
        shelveFileName = os.path.join(self.directoryPath,
                                      baseFileName+'.shelve')
        return shelveFileName
    def hasFile(self,fileName):
        metadata = self.getMetadata()
        absFileName = os.path.abspath(fileName)
        return metadata['files'].has_key(absFileName.lower())
    def addFile(self,fileName):
        metadata = self.getMetadata()
        sFileName = self.getShelveFileName(fileName)
        absFileName = os.path.abspath(fileName)
        metadata['files'][absFileName.lower()] = sFileName
        metadata.sync()
        return sFileName
        

###################################################################
# Parser class for possibly multiple types of network data, the
#   information in the file should then be stored as a shelve file
###################################################################
class Parser:
    def __init__(self,workspace=None):
        pass
    def getShelveFileName(self,fileName):
        return 
    def BuildShelveFile(self,fileName):
        # Should parse the file and return a synced shelve file object
        pass
    
class PenRegParser(Parser):
    currentVersion = 0.20
    def BuildShelveFile(self,fileName,shelveFileName):
        # Build dictionary/shelve renderings of original PenReg file
        # and timeline
        if not os.path.exists(fileName):
            return None
        shelveFile = shelve.open(shelveFileName)
        needRebuild = False
        if ((not shelveFile.has_key('syncTime')) or
            (not shelveFile.has_key('version')) ):
            needRebuild = True
        else:
            syncTime = shelveFile['syncTime']
            fileMtime = os.path.getmtime(fileName)
            if syncTime < fileMtime:
                needRebuild = True
            version = shelveFile['version']
            if version < PenRegParser.currentVersion:
                needRebuild = True
        if not needRebuild:
            # use the existing shelve file
            print 'Shelve file already prepared.'
            return shelveFile
        print 'Shelve file must be prepared, this will take a while...'

        # create a new shelve file from CSV data
        
        reader = csv.reader(open(fileName,'rb'),
                            dialect=csv.excel_tab)

        # get past file type header information
        fileType,pluginVersion,prodVersion = reader.next()
        reader.next()

        # sorting function for connection events
        def sort_by_first_element(x,y):
            if x[0]>y[0]:return 1
            if x[0]<y[0]:return -1
            return 0

        # for each packet type, build dictionary

        # 'node1' : Node1 in Pen Register file. IP address of sender.
        node1List = []
        # 'node2' : Node2 in Pen Register file. IP address of receiver.
        node2List = []
        # 'connections' : List of connections found in the PenReg
        #    file. Can be of any type (TCP, UDP or ICMP).
        connections = []
        # 'pairs' : Dictionary of node1-node2 connection
        #    pairs as keys and list of 'connections' indexes as values
        pairs = {}
        # 'connectionTypes' : 3 types stored in PenReg file. This
        #    keeps track of a list of indexes in the 'connections'
        #    list for corresponding to each connection type.
        connectionTypes = {}
        for type in ('tcp','udp','icmp'):
            connectionTypes[type] = {'connections':[],
                                     'range':None,}
        # 'totalRange' : max/min start and end times for whole PenReg
        #    file, across all types
        totalRange = {'start':None,'stop':None}

        # 'stats' : useful stats about this PenReg file
        stats = {'maxPackets':{'node1':-1e300,
                               'node2':-1e300,
                               'total':-1e300,
                               },
                 'maxBytes':{'node1':-1e300,
                             'node2':-1e300,
                             'total':-1e300,
                             },
                 'maxDuration':-1e300,
                 }
        def setup_range(rangeDict,tsDict):
            for tstype in ('start','stop'):
                if rangeDict[tstype] is None:
                    rangeDict[tstype] = [tsDict[tstype],
                                         tsDict[tstype]]
                else:
                    if tsDict[tstype]<rangeDict[tstype][0]:
                        rangeDict[tstype][0] = tsDict[tstype]
                    if tsDict[tstype]>rangeDict[tstype][1]:
                        rangeDict[tstype][1] = tsDict[tstype]
        for packetType in ['ICMP','TCP','UDP']:
            print 'Creating information on packet type:',packetType
            ptypeLower = packetType.lower()
            packetTypeHeader = reader.next()
            line = reader.next()
            # typeRange: max/min start and end times for particular
            #    connection type
            typeRange = {'start':None,'stop':None}
            while (line and (line[0] == packetType)):
                prdict = PenRegDict(line)
                ts = {'start':prdict['startGMT'],'stop':prdict['stopGMT']}
                # Find max/min start and end time for particular
                #    connection type
                setup_range(typeRange,ts)
                # Find max/min start and end time for entire PenReg
                #    file
                setup_range(totalRange,ts)

                # Put novel node1 IPs in shelve['node1'] list of IPs
                if prdict['node1']['ip'] not in node1List:
                    node1List.append(prdict['node1']['ip'])
                # Put novel node2 IPs in shelve['node2'] list of IPs
                if prdict['node2']['ip'] not in node2List:
                    node2List.append(prdict['node2']['ip'])

                # Build connection information
                indexKey = (node1List.index(prdict['node1']['ip']),
                            node2List.index(prdict['node2']['ip']))
                prdictCopy = prdict.storeCopy()
                connectInfo = [indexKey,reader.line_num,prdictCopy]
                # add connection data to shelve['connections'] list
                connections.append(connectInfo)
                line = reader.next()

            # save range for specific connection type
            connectionTypes[ptypeLower]['range'] = typeRange

        # sort connections by start time
        print 'Sorting connections'
        #connections.sort(sort_connections)
        connections.sort(sort_connections_tuples)

        # build connection pairs 
        for index,connectInfo in enumerate(connections):
            indexKey,lineNo,event = connectInfo
            # add connection pair to shelve['pairs']
            if indexKey not in pairs:
                pairs[indexKey] = {'list':[index],
                                   'bytes': int(event[-5].strip() or 0)}
            else:
                pairs[indexKey]['list'].append(index)
                pairs[indexKey]['bytes']+=int(event[-5].strip() or 0)

        print 'Sorting connection pairs'
        # 'pairsSorted' : List of node1/node2 indexes pairs sorted
        #    by total number found in the PenReg file
        pairsSorted = pairs.keys()
        def sort_connection_pairs(x,y):
            lenx,leny = len(pairs[x]['list']),len(pairs[y]['list'])
            if lenx > leny : return 1
            if lenx < leny : return -1
            return 0
        pairsSorted.sort(sort_connection_pairs)

        print 'Syncing shelve file'
        shelveFile['node1'] = node1List 
        shelveFile['node2'] = node2List
        shelveFile['connections'] = connections
        shelveFile['pairs'] = pairs 
        shelveFile['pairsSorted'] = pairsSorted
        shelveFile['connectionTypes'] = connectionTypes
        shelveFile['totalRange'] = totalRange
        shelveFile['stats'] = stats
        shelveFile['syncTime'] = time.time()
        shelveFile['version'] = PenRegParser.currentVersion
        shelveFile.sync()

        return shelveFile
        
class NetworkEventTimeline(dict):
    initialised = False
    def isInitialised(self):
        return self.initialised
    def getTalker(self,index):
        return self['node1'][index]
    def getListener(self,index):
        return self['node2'][index]
    def buildTimeline(self,numSlices,shelveFile=None):
        print 'Importing shelve file contents into memory'
        print 'Number of slices:',numSlices
        self.numSlices = numSlices
        if shelveFile is not None:
            for key in shelveFile.keys():
                print '-------> ',key
                self[key] = shelveFile[key]
        else:
            print 'No shelvefile to parse, continuing...'
            return
        print 'Building network event timeline'
        def listify(object):
            if type(object) in [type([]),type(())]:
                object = list(object)
                for i,item in enumerate(object):
                    object[i] = listify(item)
            return object
        connections = self['connections'] = list(self['connections'])
        print 'Parsing events...'
        for i,connection in enumerate(connections):
            if not (i%10000):
                print 'Up to event no.',i
            indexKey,lineNo,conInfo = connection
            prdict = PenRegDict()
            prdict.fromStoreCopy(conInfo)
            connections[i] = [indexKey,lineNo,prdict]
        for index,(indexKey,lineNo,conInfo) in enumerate(connections):
            ip1,ip2 = (self['node1'][indexKey[0]],
                       self['node2'][indexKey[1]])
            conInfo.setNode1IP(ip1)
            conInfo.setNode2IP(ip2)
        
        numConnections = len(connections)
        print 'Number of connection events:',numConnections
        start = self['totalRange']['start']
        totalTime = self['totalTime'] = start[1]-start[0]
        sliceDuration = self['sliceDuration'] = totalTime/numSlices
        sliceLookup = self['sliceLookup'] = []
        currentTime = start[0]
        currentIndex = 0
        for i in range(numSlices):
            sliceLookup.append([])
            sliceBegin,sliceEnd = currentTime,currentTime+sliceDuration
            while(connections[currentIndex][2]['startGMT'] <= sliceEnd):
                sliceLookup[i].append(currentIndex)
                currentIndex += 1
                if currentIndex == len(connections):
                    break
            currentTime += sliceDuration

        # Get stats for shelve file
        self['stats'] = stats = {'maxPackets':{'node1':[-1e300,-1],
                                               'node2':[-1e300,-1],
                                               'total':[-1e300,-1],
                                               },
                                 'maxBytes':{'node1':[-1e300,-1],
                                             'node2':[-1e300,-1],
                                             'total':[-1e300,-1],
                                             },
                                 'maxDuration':[-1e300,-1],
                                 }
        for index,connectInfo in enumerate(connections):
            indexKey,lineNo,prdict = connectInfo
            # Gather useful stats
            tp,tb,dur = (int(prdict['totalPackets']),
                         int(prdict['totalBytes']),
                         int(prdict['duration']))
            if tp>stats['maxPackets']['total'][0]:
                stats['maxPackets']['total'] = [tp,index]
            if tb>stats['maxBytes']['total'][0]:
                stats['maxBytes']['total'] = [tb,index]
            if dur>stats['maxDuration'][0]:
                stats['maxDuration'] = [dur,index]
            n1p,n1b,n2p,n2b = (int(prdict['node1']['packets']),
                               int(prdict['node1']['bytes']),
                               int(prdict['node2']['packets']),
                               int(prdict['node2']['bytes']) )
            if n1p>stats['maxPackets']['node1'][0]:
                stats['maxPackets']['node1'] = [n1p,index]
            if n1b>stats['maxBytes']['node1'][0]:
                stats['maxBytes']['node1'] = [n1b,index]
            if n2p>stats['maxPackets']['node2'][0]:
                stats['maxPackets']['node2'] = [n2p,index]
            if n2b>stats['maxBytes']['node2'][0]:
                stats['maxBytes']['node2'] = [n2b,index]
        #pprint.pprint( stats)
                    
        # Get top port breakdown
        ports = self['ports'] = {'icmp':0,
                                 'udp':0,
                                 }
        for i,(indexKey,lineNo,connectInfo) in enumerate(connections):
            n1p = connectInfo['node1']['logical']
            n2p = connectInfo['node2']['logical']
            if connectInfo['type'] == 'icmp':
                ports['icmp'] += 1
            elif connectInfo['type'] == 'udp':
                ports['udp'] += 1
            else:
                n1p,n2p = int(n1p),int(n2p)
#                 if (n1p not in ports):
#                     ports[n1p] = 1
#                 else:
#                     ports[n1p] += 1
                if (n2p not in ports):
                    ports[n2p] = 1
                else:
                    ports[n2p] += 1
        portsSorted = self['portsSorted'] = ports.keys()
        def sortPorts(x,y):
            if ports[x] > ports[y]:
                return 1
            if ports[y] > ports[x]:
                return -1
            return 0
        portsSorted.sort(sortPorts)
        for port, num in ports.items():
            ports[port] = {'num':num,
                           'percent':float(num)/numConnections}
        #for port in portsSorted:
        #    print port,ports[port]['num'],ports[port]['percent']

        # Get top talkers (node1), listeners (node2)
        talkers = self['talkers'] = {}
        listeners = self['listeners'] = {}
        for ip1 in self['node1']:
            talkers[tuple(ip1)] = {'num':0,
                                   'percent':0}
        for ip2 in self['node2']:
            listeners[tuple(ip2)] = {'num':0,
                                     'percent':0}
        for i,(indexKey,lineNo,connectInfo) in enumerate(connections):
            ip1,ip2 = (connectInfo['node1']['ip'],
                       connectInfo['node2']['ip'])
            ip1,ip2 = tuple(ip1),tuple(ip2)
            talkers[ip1]['num'] += 1
            listeners[ip2]['num'] += 1
        talkersSorted = self['talkersSorted'] = talkers.keys()
        def sortTalkers(x,y):
            if talkers[x]['num'] > talkers[y]['num']:
                return 1
            if talkers[y]['num'] > talkers[x]['num']:
                return -1
            return 0
        talkersSorted.sort(sortTalkers)
        for talker, info in talkers.items():
            talkers[talker]['percent'] = float(info['num'])/numConnections
        listenersSorted = self['listenersSorted'] = listeners.keys()
        def sortListeners(x,y):
            if listeners[x]['num'] > listeners[y]['num']:
                return 1
            if listeners[y]['num'] > listeners[x]['num']:
                return -1
            return 0
        listenersSorted.sort(sortListeners)
        for listener, info in listeners.items():
            listeners[listener]['percent'] = float(info['num'])/numConnections

        self.initialised = True

    def getFilterStatistics(self, eventFilter):
        efHash = hash(eventFilter)
        if 'filter' not in self['stats']:
            self['stats']['filter'] = {}
        if efHash in self['stats']['filter']:
            return self['stats']['filter'][efHash]
        else:
            self['stats']['filter'][efHash] = stats = {
                'locations':[],
                'frequency':{},
                'firstEvent':{}
                }
            print 'getting filter stats',repr(efHash)
            for i in range(self.numSlices):
                sliceEventIndex = 0
                for j in self['sliceLookup'][i]:
                    indexKey,lineNo,event = self['connections'][j]
                    if eventFilter.testEvent(event):
                        loc = stats['locations']
                        if i not in loc:
                            loc.append(i)
                            stats['firstEvent'][i] = (j,sliceEventIndex)
                        freq = stats['frequency']
                        if i not in freq:
                            freq[i] = 0
                        freq[i]+=1
                    sliceEventIndex += 1
            for stats in self['stats']['filter'].values():
                freq = stats['frequency'].values()
                maxEvents=0
                if freq:
                    maxEvents = max(freq)
                stats['maxEvents'] = maxEvents
                    
        return self['stats']['filter'][efHash]
        

    def getSliceInfo(self,sliceNum):
        slice = {'events':self['sliceLookup'][sliceNum]}
        return slice

    def getEventSlice(self,eventIndex,lookBack,lookAhead,filter=None):
        connections = self['connections']
        if eventIndex<lookBack:
            lookBackSlice = connections[0:eventIndex]
        else:
            lookBackSlice = connections[eventIndex-lookBack:eventIndex]
        if eventIndex+lookAhead>=len(connections):
            lookAheadSlice = connections[eventIndex:]
        else:
            lookAheadSlice = connections[eventIndex:eventIndex+lookAhead]
        retSlice = [lookBackSlice,lookAheadSlice]
        currentEvents,lookAheadSlice = [lookAheadSlice[0]],lookAheadSlice[1:]
        for event in list(lookAheadSlice):
            ikey,line,conInfo = event
            if conInfo['startGMT'] == currentEvents[0][-1]['startGMT']:
                currentEvents.append(event)
                lookAheadSlice = lookAheadSlice[1:]
            else:
                break
        retSlice = [lookBackSlice,currentEvents,lookAheadSlice]
        return retSlice

    def sort_slice(self,x,y):
        if x[1]>y[1]: return 1
        if x[1]<y[1]: return -1
        return 0

class EventFilter(dict):
    __ready = False
    filterKeys = ('talker','listener','port','bytes')
    def __init__(self,**kw):
        self['isolate'] = 1
        self['graph'] = 1
        self.update(kw)
        for key in EventFilter.filterKeys:
            if key not in self:
                self[key] = ''
        self.__ready = True
    def ready(self):
        return self.__ready
    def __hash__(self):
        talker = tuple(self['talker']) if self['talker'] else ''
        listener = tuple(self['listener']) if self['listener'] else ''
        port = self['port']
        bytes = tuple(self['bytes']) if self['bytes'] else ''
        return hash((talker,listener,port,bytes))
    def update(self,kw):
        for ipType in ('talker','listener'):
            if (ipType in kw) and kw[ipType]:
                ip = kw[ipType]
                ipSplit = ip.split('.')
                if len(ipSplit) < 4:
                    for i in range(4-len(ipSplit)):
                        ipSplit.append('*')
                elif len(ipSplit) > 4:
                    err = ('Incorrectly formatted IP address.'
                           ' Only 4 octets, please')
                    raise SyntaxError,err
                for i,value in enumerate(ipSplit):
                    if value.isdigit():
                        ipSplit[i] = int(value)
                    elif '!' in value:
                        value = value.replace('!','')
                        if not value.isdigit():
                            err = ('Incorrectly formatted IP address.'
                                   ' Not symbol ("!") must precede'
                                   ' integer octet designation.')
                            raise SyntaxError,err
                    else:
                        ipSplit[i] = '*'
                self[ipType] = ipSplit
        if ('bytes' in kw) and kw['bytes']:
            bytes = kw['bytes']
            foundOp = False
            for opString in ('>','<','==','!=','>=','<='):
                if opString in bytes:
                    bytesSplit = bytes.split(opString)
                    if len(bytesSplit) != 2:
                        err = 'Only one operator per filter, please'
                        raise SyntaxError,err
                    self['bytes'] = [opString,int(bytesSplit[-1])]
                    foundOp = True
                    break
            if not foundOp:
                if not bytes.isdigit():
                    err = 'Incorrectly formatted bytes designation'
                    raise SyntaxError,err
                self['bytes'] = ['==',int(bytes)]
        if ('port' in kw) and kw['port']:
            if kw['port'].isdigit():
                self['port'] = int(kw['port'])
            elif kw['port'] not in ('icmp','udp'):
                err = 'Not a correct port designation: %s'%kw['port']
                raise SyntaxError,err
            else:
                self['port'] = kw['port']
        for mod in ('isolate','graph'):
            if mod in kw:
                self[mod] = kw[mod]
    def isIsolate(self):
        return self['isolate']
    def isGraph(self):
        return self['graph']
    def testEvent(self,event):
        if not self.ready():
            #print 'Problem in setting up this event filter!'
            return True
        if self['port']:
            port = event['node2']['logical']
            if event['type'] in ('udp','icmp'):
                port = event['type']
            else:
                port = int(port)
            #print port,self['port']
            if port != self['port']:
                return False
        nodes = (event['node1']['ip'],
                 event['node2']['ip'])
        ipTypes = ('talker','listener')
        for nindex,nodeType in enumerate(nodes):
            ipType = ipTypes[nindex]
            if self[ipType]:
                for i,octetValue in enumerate(nodeType):
                    try:
                        if (self[ipType][i] is '*'):
                            continue
                        if octetValue != self[ipType][i]:
                            return False
                    except:
                        print i,self[ipType],nodeType
                        raise
        if self['bytes']:
            opString,testBytes = self['bytes']
            bytes = event['totalBytes']
            if not bytes.isdigit(): return False
            bytes = int(bytes)
            return eval ('bytes %s testBytes'%opString)
        return True

class NetviewTk(PanedWindow):
    def __init__(self,master=None,**kw):
        PanedWindow.__init__(self,master)
        self.__dict__.update(kw)
        self.guiInitialized = False
        self.timeline = NetworkEventTimeline()
        self.state = {
            'sliceInfo':None,
            'canvasMouse':(0,0),
            'selectBox':{'bbox':[ [0,0],
                                  [0,0] ],
                         'begun':False,
                         },
            'pickBox':{'bbox':[ [0,0],
                                [0,0] ],
                       #'begun':False,
                       },
            'topProtocolsList': {'indexes': [],
                                 },
            'topPairsList': {'indexes': [],
                               },
            'topTalkersList': {'indexes': [],
                               },
            'topListenersList': {'indexes': [],
                                 },
            'selectedEventsList': {'indexes': [],
                                   },
            'eventFilterList': {'indexes': [],
                                },
            'eventFilterInput': {'talker': None,
                                 'listener': None,
                                 'port': None,
                                 'bytes': None,
                                 },
            'eventFilterGraph': {'highlightedSlice':None,
                                 'selectedSlice':None,
                                 },
            'selected': {'topProtocols': [],
                         'topTalkers': [],
                         'topPairs': [],
                         'topListeners': [],
                         'eventFilters': [],
                         'selectedEvents': [], # events selected from
                                               # selectedEventsList
                                               # listbox
                         'events': [], # events selected from the
                                       # canvas
                         },
            'events': [], # list of events currently being displayed
            'canvas': {'lines':{},
                       'ids':[],
                       },
            'graph': {},
            }
        self.current = {'lookBack':100,
                        'lookAhead':20,
                        'numSlices':200,
                        'fileName':'',
                        'step':None,
                        'pickBox':{'size':3,
                                   },
                        'filter': [],                        
                        'display': {'default': [{'arrow':[[3,12,3],
                                                        [8,10,3],
                                                        [3,12,3],
                                                        ],
                                                 'width':[1,5,1],
                                                 'color':[[.5,.5,.5],
                                                          [1,0,1],
                                                          [0,0,1],
                                                          ],
                                                 },
                                                {'arrow':[[3,14,6],
                                                          [3,14,6],
                                                          [3,14,6],
                                                          ],
                                                 'width':[7,7,7],
                                                 },], 
                                    'ports': {21:{'name':'FTP',
                                                  },
                                              22:{'name':'SSH',
                                                  },
                                              23:{'name':'Telnet',
                                                  },
                                              25:{'name':'SMTP',
                                                  },
                                              53:{'name':'DNS',
                                                  },
                                              443:{'name':'HTTPS',
                                                  },
                                              4448:{'color':[[0,1,0],
                                                             [1,0,1],
                                                             [0,1,0],
                                                             ],
                                                    },
                                              'icmp':{'name':'ICMP',
                                                      'color':[[0,.6,0],
                                                               [1,0,1],
                                                               [0,1,0],
                                                               ],
                                                      },
                                              'udp':{'name':'UDP',
                                                      },
                                              },
                                    'portOrder':[22,25,21,53,23,443],
                                    },
                        }
        self.workspace = Workspace()
        metadata = self.workspace.getMetadata()
        settings = metadata.getSettings()
        if settings:
            for key,value in settings.items():
                self.current[key] = value
        if __name__=='__main__':
            self.parse_args()
        if self.current['fileName']:
            self.OpenFile(self.current['fileName'])
        if not self.guiInitialized:
            self.SetupGUI()
        self.syncMetadata()

    def parse_args(self):
        op = optparse.OptionParser()
        op.add_option('-f','--file',dest='initFileName',
                      help='Data file to parse',
                      #default='IPPenRegister.test.txt',
                      )
        op.add_option('-v','--verbose',dest='verbose',
                      default=True)
        op.add_option('--width',dest='initWidth',
                      default=600,type='int')
        op.add_option('--height',dest='initHeight',
                      default=600,type='int')
        op.add_option('--slices',dest='initSlices',
                      #default=150,
                      type='int')
        op.add_option('-a','--ahead',dest='initLookAhead',
                      #default=20,
                      type='int')
        op.add_option('-b','--behind',dest='initLookBehind',
                      #default=100,
                      type='int')
        (options,args) = op.parse_args()
        self.__dict__.update(options.__dict__)
        if self.initLookBehind:
            self.current['lookBack'] = self.initLookBehind
        if self.initLookAhead:
            self.current['lookAhead'] = self.initLookAhead
        if self.initSlices:
            self.current['numSlices'] = self.initSlices
        if self.initFileName:
            self.current['fileName'] = self.initFileName

    #-------------------------------------------
    # State methods
    def OpenFile(self,fileName):
        # if pen register file
        parser = PenRegParser()
        shelveFileName = self.workspace.addFile(fileName)
        self.shelve = parser.BuildShelveFile(fileName,shelveFileName)
        self.buildTimeline()
        self.current['fileName'] = fileName
        
    def buildTimeline(self):
        self.timeline.buildTimeline(self.current['numSlices'],self.shelve)
            
        
    #-------------------------------------------
    # Metadata methods (i.e. methods on the "current" dictionary) 
    def syncMetadata(self):
        # Sync the metadata file
        print 'Syncing Metadata File'
        metadata = self.workspace.getMetadata()
        for key, value in self.current.items():
            metadata.setSetting(key,value)
        metadata.sync()
    def getImportantProtocolsDisplayInfo(self):
        displayPorts = self.current['display']['ports']
        return displayPorts
    def getImportantProtocolsDisplayOrder(self):
        return self.current['display']['portOrder']
    def getDefaultHttpDisplayInfo(self):
        return self.current['display']['default'][0]
    def getDefaultNonHttpDisplayInfo(self):
        return self.current['display']['default'][1]
    def setImportantProtocolColor(self,port,color):
        displayPorts = self.getImportantProtocolsDisplayInfo()
        self.tagProtocolAsImportant(port)
        displayPorts[port]['color'] = color
    def setImportantProtocolName(self,port,name):
        displayPorts = self.getImportantProtocolsDisplayInfo()
        self.tagProtocolAsImportant(port)
        displayPorts[port]['name'] = name

    #---------------------------------------------------------------
    # Retrieve and parse a new data file
    def getNewFile(self):
        fileDialog = wx.FileDialog(
            self,message='Please select IP Pen Trap filename to open',
            defaultDir=os.getcwd(),
            wildcard='IP Pen Trap (*.txt)|*.txt',
            style=wx.OPEN | wx.CHANGE_DIR,
            )

        if fileDialog.ShowModal() == wx.ID_OK:
            path = fileDialog.GetPath()
            if not path: return
            top = self.winfo_toplevel()
            top.title(path)
            self.OpenFile(path)
            self.reset()
            self.syncMetadata()

    #-----------------------------------------------------------------
    # Set the animation step for the network visualization.
    #
    # [04-02] need to find some way to make this fast [done]
    # --XXX-- still needs to be faster for large values of
    #    "lookback" [4-02]
    def set_time_slice(self,timeSlice):
        timeSlice = int(timeSlice)
        self.current['timeSlice'] = timeSlice - 1

        if not self.timeline.isInitialised(): return

        sliceInfo = self.timeline.getSliceInfo(timeSlice-1)
        self.state['sliceInfo'] = sliceInfo
        numEvents = len(sliceInfo['events'])
        #print 'Number of events in slice',timeSlice,'is:',numEvents
        self.setEventFilterGraphTimeSlice(timeSlice-1)
        self.eventSlider.configure(to=numEvents)
        self.eventSlider.set(0)
        self.set_event(1)
        #print len(sliceInfo['events'])

    #-----------------------------------------------------------------
    # Set the individual event
    def set_event(self,step):
        step = int(step)-1
        self.current['step'] = step
        self.refreshMainCanvas()
        self.refreshSelectList()
        
    def getCanvasSize(self):
        return self.mainCanvas.winfo_width(),self.mainCanvas.winfo_height()
        
    def get_connect_coords(self,connectInfo):
        #source,dest = connectInfo[:2]
        source,dest = (connectInfo['node1'],
                       connectInfo['node2'],)
        w,h = self.getCanvasSize()
        grid = w/256.,h/256.
        #sx,sy = source[0][:2]
        #dx,dy = dest[0][:2]
        sx,sy = source['ip'][:2]
        dx,dy = dest['ip'][:2]
        #print sx,sy,dx,dy
        sx*=grid[0];sy*=grid[1]
        dx*=grid[0];dy*=grid[1]
        return sx,sy,dx,dy,grid
    def to_color_string(self,red,green=None,blue=None):
        # red, green, blue parameterized [0..1]
        if green is None:
            green=blue=red
        red*=4095;green*=4095;blue*=4095;
        red=int(red);green=int(green);blue=int(blue);
        color = '#'
        #print red,green,blue
        color += (hex(red)[2:].zfill(3)+
                  hex(green)[2:].zfill(3)+
                  hex(blue)[2:].zfill(3))
        return color

    #-----------------------------------------------------------------
    # Event methods
    def getEventFromCanvasId(self,ID):
        event = self.state['canvas']['lines'].get(ID,None)
        return event
    def getCurrentEventSet(self):
        rlist = []
        for id,event in self.state['canvas']['lines'].items():
            rlist.append(event)
        return rlist
    def getCurrentEventIds(self):
        return self.state['canvas']['ids']
    def addCurrentEvent(self,event,canvasID):
        self.state['events'].append(event)
        self.state['canvas']['lines'][canvasID] = event
        self.state['canvas']['ids'].append(canvasID)
    def clearCurrentEvents(self):
        self.state['events'] = []
        for lineID in self.state['canvas']['ids']:
            self.mainCanvas.delete(lineID)
        self.state['canvas']['lines'] = {}
        self.state['canvas']['ids'] = []

    #---------------------------------------------------------------
    # GUI Refresh methods
    #---------------------------------------------------------------

    #-----------------------------------------------------------------
    # Canvas refresh
    def refreshMainCanvas(self):

        if not self.timeline.isInitialised(): return

        sliceInfo = self.state.get('sliceInfo', None)
        if sliceInfo is None: return
        step = self.current.get('step',None)
        if step is None: return
        lookBack,lookAhead = (self.current['lookBack'],
                              self.current['lookAhead'])
        #eventFilter = self.current.get('filter',None)
        eventIndex = sliceInfo['events'][step]
        eventSlice = self.timeline.getEventSlice(eventIndex,
                                                 lookBack,
                                                 lookAhead,
                                                 filter=None)
        lookBackSlice,event,lookAheadSlice = eventSlice
        lookAheadSlice.reverse()

        # remove existing lines
        self.clearCurrentEvents()
        # unhighlight any currently selected events
        self.clearHighlightedEvents()

        # Draw the graph of selected items
        #self.refreshFrequencyGraph()

        default = self.getDefaultHttpDisplayInfo()
        nonhttp = self.getDefaultNonHttpDisplayInfo()
        displayPorts = self.getImportantProtocolsDisplayInfo()
        icmp = displayPorts['icmp']
        udp = displayPorts['udp']
        guiIndexOffset = len(eventSlice[1])-1
        #for i,slice in enumerate(eventSlice):
        for i in (0,2,1):
            slice = eventSlice[i]
            lenSlice = len(slice)
            for j,(indexKey,lineNo,event) in enumerate(slice):
                displayDict = copy.deepcopy(default)
                if event['node2']['port'] != 'http':
                    # non-http event, might be special, highlight
                    displayDict.update(copy.deepcopy(nonhttp))
                    portString = event['node2']['logical'].strip()
                    if portString:
                        port = int(portString)
                        if port in displayPorts:
                            portInfo = displayPorts[port]
                            displayDict.update(copy.deepcopy(portInfo))
                        elif event['type'] == 'udp':
                            displayDict.update(copy.deepcopy(udp))
                    else:
                        displayDict.update(copy.deepcopy(icmp))
                color = displayDict['color'][i]
                arrowShape = tuple(displayDict['arrow'][i])
                arrowType = LAST
                """
                if int(event['totalBytes'])>30000:
                    arrowShape = list(arrowShape)
                    for i,v in enumerate(arrowShape):
                        arrowShape[i] = 5*v
                    #arrowShape[0] = -arrowShape[0]
                    arrowShape = tuple(arrowShape)
                    arrowType = FIRST
                """
                width = displayDict['width'][i]
                percentOfWhite = float(j+1)/lenSlice
                # XXX ugly hack
                if i==0: # look back
                    for k in range(3):
                        color[k] = (1-color[k])*(1-percentOfWhite)+color[k]
                elif i==2: # look ahead
                    for k in range(3):
                        color[k] = (1-color[k])*(1-percentOfWhite)+color[k]
                color = self.to_color_string(color[0],color[1],color[2])
                sx,sy,dx,dy,grid = self.get_connect_coords(event)
                id = 0
#                 isolatedProtocols = eventFilter['isolate']['protocols']
#                 if ( (not isolatedProtocols) or 
#                      ( (event.getPort(0) in isolatedProtocols) or
#                        (event.getPort(1) in isolatedProtocols) )
#                      ):
#                     id = self.mainCanvas.create_line(
#                         sx,sy,dx,dy,
#                         arrow=LAST,
#                         arrowshape=arrowShape,
#                         fill=color,width=width,
#                         )
                if self.testEvent(event):
                    id = self.mainCanvas.create_line(
                        sx,sy,dx,dy,
                        arrow=arrowType,
                        arrowshape=arrowShape,
                        fill=color,width=width,
                        )
                # get index
                guiIndex = 0
                if i == 0:
                    # lookBack
                    guiIndex = j-lenSlice
                elif i==1:
                    #guiIndexOffset+=1
                    guiIndex = j/100.
                elif i==2:
                    # lookAhead
                    guiIndex = lenSlice-j+guiIndexOffset
                event['guiInfo'] = {'vector':(sx,sy,dx,dy,grid),
                                    'alpha':percentOfWhite,
                                    'id':id,
                                    'index':guiIndex,
                                    }
                self.addCurrentEvent(event,id)

    #---------------------------------------------------------------
    # Reset the GUI
    def reset(self):
        self.clearSelectedEvents()
        self.resetInfoFrame()
        self.update_idletasks()
        self.set_time_slice(1)
        self.refreshAllCanvases()


    #---------------------------------------------------------------
    # GUI setup
    #---------------------------------------------------------------

    #_______________________________________________________________
    #   Pick box methods
    def getPickBoxItems(self):
        p0,p1 = self.getPickBoxBbox()
        items = list(self.mainCanvas.find_overlapping(p0[0],p0[1],p1[0],p1[1]))
        id = self.getPickBoxId()
        if id in items:
            items.remove(id)
        return items
    def getPickBoxSize(self):
        return self.current['pickBox']['size']
    def setPickBoxBbox(self,p0,p1):
        self.state['pickBox']['bbox'] = [list(p0),list(p1)]
    def getPickBoxBbox(self):
        return self.state['pickBox']['bbox']
    def getPickBoxId(self):
        return self.state['pickBox'].get('id',None)
    def setPickBoxId(self,canvasID):
        self.state['pickBox']['id'] = canvasID
    def drawPickBox(self,x,y):
        id = self.getPickBoxId()
        d = self.getPickBoxSize()
        if id is None:
            p0 = [x-d,y-d]
            p1 = [x+d,y+d]
            id = self.mainCanvas.create_rectangle(x-d,y-d,x+d,y+d,
                                              outline='red')
            self.setPickBoxId(id)
            self.setPickBoxBbox(p0,p1)
        else:
            self.setPickBoxBbox((x-d,y-d),(x+d,y+d))
            self.mainCanvas.coords(id,x-d,y-d,x+d,y+d)
    #---------------------------------------------------------------
            
    #_______________________________________________________________
    #   Select box methods
    def getSelectBoxItems(self):
        p0,p1 = self.getSelectBoxBbox()
        items = self.mainCanvas.find_overlapping(p0[0],p0[1],p1[0],p1[1])
        return items
    def setSelectBoxBbox(self,p0,p1):
        self.state['selectBox']['bbox'] = [list(p0),list(p1)]
    def getSelectBoxBbox(self):
        return self.state['selectBox']['bbox']
    def beginSelectBox(self,x,y):
        self.setSelectBoxBbox([x,y],[x,y])
        id = self.mainCanvas.create_rectangle(x,y,x,y,fill='')
        self.setSelectBoxId(id)
        self.state['selectBox']['begun'] = True
    def adjustSelectBox(self,x,y):
        if self.state['selectBox']['begun']:
            p0,p1 = self.getSelectBoxBbox()
            p1 = [x,y]
            id = self.getSelectBoxId()
            self.setSelectBoxBbox(p0,p1)
            self.mainCanvas.coords(id,p0[0],p0[1],p1[0],p1[1])
            IDs = self.mainCanvas.find_overlapping(p0[0],p0[1],p1[0],p1[1])
            self.highlight_events_by_canvas_ids(IDs)
    def endSelectBox(self,x,y):
        if self.state['selectBox']['begun']:
            p0,p1 = self.getSelectBoxBbox()
            p1 = [x,y]
            self.setSelectBoxBbox(p0,p1)
            id = self.getSelectBoxId()
            self.mainCanvas.delete(id)
            self.setSelectBoxId(None)
            IDs = self.mainCanvas.find_overlapping(p0[0],p0[1],p1[0],p1[1])
            if not IDs:
                # clear selected items
                self.clearSelectedEvents()
                self.clearHighlightedEvents()
            else:
                self.select_events_by_canvas_ids(IDs)
            self.state['selectBox']['begun'] = False
    def getSelectBoxId(self):
        return self.state['selectBox']['id']
    def setSelectBoxId(self,canvasID):
        self.state['selectBox']['id'] = canvasID
    #---------------------------------------------------------------
        
    #_______________________________________________________________
    # highlight events
    def highlight_events_by_bbox(self,x0,y0,x1,y1):
        items = self.mainCanvas.find_overlapping(x0,y0,x1,y1)
        if items:
            self.highlight_events_by_canvas_ids(items)
    def highlight_events_by_canvas_ids(self,IDs):
        # unhighlight any currently selected events
        self.clearHighlightedEvents()

        # store information for newly selected ids
        colors,dropLines,ipLabels = [],[],[]
        ips = {}
        for ID in IDs:
            color,dropLineID,ipLabelID = None,None,None
            if ID in self.getCurrentEventIds():
                color = self.mainCanvas.itemcget(ID,'fill')
                self.mainCanvas.itemconfigure(ID,fill='red')
                self.mainCanvas.tag_raise(ID)
                conInfo = self.getEventFromCanvasId(ID)
                (sx,sy,dx,dy,grid) = conInfo['guiInfo']['vector']
                w,h = self.getCanvasSize()
                dropLineID = self.mainCanvas.create_line(sx,sy,sx,h,
                                                     fill='red',)
                n1ip,n2ip = (tuple(conInfo['node1']['ip']),
                             tuple(conInfo['node1']['ip']))
                ip = '.'.join(map(str,n1ip))
                if conInfo['node2']['port'] != 'http':
                    ip +=', '+ '.'.join(map(str,conInfo['node2']['ip']))
                    ip +=', '+ conInfo['node2']['port']
                if n1ip in ips:
                    ips[n1ip]['num'] += 1
                    ip += '('+str(ips[n1ip]['num'])+')'
                    self.mainCanvas.itemconfigure(ips[n1ip]['id'],
                                              text=ip)
                else:
                    ipLabelID = self.mainCanvas.create_text(sx,sy,text=ip,anchor=SW)
                    x0,y0,x1,y1 = self.mainCanvas.bbox(ipLabelID)
                    #print 'w,h',w,h
                    #print 'bbox',x0,y0,x1,y1
                    dx,dy = 0,0
                    if x0<0:
                        dx = 0-(x0-5)
                    if x1>w:
                        dx = w-(x1+5)
                    if y0<0:
                        dy = 0-(y0-5)
                    if y1>h:
                        dy = h-(y1+5)
                    #print 'dx,dy',dx,dy
                    #print
                    x0,y0 = self.mainCanvas.coords(ipLabelID)
                    x0+=dx;y0+=dy
                    self.mainCanvas.coords(ipLabelID,x0,y0)
                    ips[n1ip] = {'num':1,
                                 'id':ipLabelID}
            colors.append(color)
            dropLines.append(dropLineID)
            ipLabels.append(ipLabelID)
        objectList = [dropLines,ipLabels]
        self.state['highlightedEvents'] = (IDs,colors,objectList)
    def clearHighlightedEvents(self):
        currentInfo = self.state.get('highlightedEvents',
                                     (None,None,None))
        currentIDs,currentColors,objectList = currentInfo
        if (currentIDs is not None):
            for i,ID in enumerate(currentIDs):
                color = currentColors[i]
                self.mainCanvas.itemconfigure(ID,fill=color)
            for objects in objectList:
                for objectID in objects:
                    if objectID is not None:
                        self.mainCanvas.delete(objectID)
            #self.mainCanvas.update_idletasks()
            del self.state['highlightedEvents']
    #---------------------------------------------------------------

    #_______________________________________________________________
    # select events
    def clearSelectedEvents(self):
        self.selectedEventsList.clear()
        self.state['selectedEventsList']['indexes'] = []
        self.state['selected']['selectedEvents'] = []
        self.state['selected']['events'] = []

    def setupSelectList(self,parent):
        slFrame = Frame(parent,bd=2,relief='sunken')
        mlbFrame = Frame(slFrame)
        headings = (
            ('Index',3),('Time',12),('Talker',12),
            ('Listener',12),('Port',4),('Bytes',3),
            )
        self.selectedEventsList = MultiListbox(
            mlbFrame,headings,
            height=10,
            command=self.select_list_callback,
            label='Selected Events')
        mlbFrame.pack(fill=BOTH,expand=1)
        return slFrame
    def setSelectedEvents(self,events):
        self.state['selected']['events'] = events
    def getSelectedEvents(self):
        return self.state['selected']['events']
    def getSelectedEventsListEvents(self):
        listIndexes = self.state['selectedEventsList']['indexes']
        events = self.getSelectedEvents()
        returnEvents = []
        if events:
            IDs = []
            for listIndex in listIndexes:
                index = int(listIndex)
                returnEvents.append(events[index])
        return returnEvents
    def select_list_callback(self,listIndexes):
        self.state['selectedEventsList']['indexes'] = listIndexes
        events = self.getSelectedEventsListEvents()
        if events:
            IDs = []
            for event in events:
                IDs.append(event['guiInfo']['id'])
            self.highlight_events_by_canvas_ids(IDs)
    def get_select_list_item_from_event(self,event):
        index = str(event['guiInfo']['index'])
        time = event['startGMT']
        timeString = (time.strftime('%H:%M:%S')+
                      ('%.4f'%(time.microsecond/1000000.))[1:])
        ip1,ip2 = (event.getIpString('node1'),
                   event.getIpString('node2'),)
        port = event['node2']['port']
        bytes = event['totalBytes']
        return (index,timeString,ip1,ip2,port,bytes)
    def select_events_by_canvas_ids(self,IDs):
        self.clearSelectedEvents()
        self.clearHighlightedEvents()
        events = []
        for ID in IDs:
            event = self.getEventFromCanvasId(ID)
            if event:
                events.append(event)
        self.highlight_events_by_canvas_ids(IDs)
        events.sort(sort_events)
        self.setSelectedEvents(events)
        self.refreshSelectList()
    def refreshSelectList(self):
        self.selectedEventsList.clear()
        events = self.getSelectedEvents()
        if events:
            currentEvents = self.getCurrentEventSet()
            for i,event in enumerate(events):
                if not event in currentEvents:
                    continue
                listItem = self.get_select_list_item_from_event(event)
                #print listItem
                self.selectedEventsList.insert(END,listItem,indexHash=i)
    def select_events_by_bbox(self,x0,y0,x1,y1):
        items = self.mainCanvas.find_overlapping(x0,y0,x1,y1)
        if items:
            self.select_events_by_canvas_ids(items)
    #---------------------------------------------------------------

    #------------------------------------------#####################
    #------------------------------------------#####################
    # Event Callbacks

    #_______________________
    #   motion event:
    def canvas_motion_event(self,event):
        canvas = event.widget
        x,y = canvas.canvasx(event.x),canvas.canvasy(event.y)
        self.state['canvasMouse']=(x,y)
        self.drawPickBox(x,y)
        items = self.getPickBoxItems()
        if not self.getSelectedEvents():
            if items:
                self.highlight_events_by_canvas_ids(items)
            else:
                self.clearHighlightedEvents()
        
    #_______________________
    #   button1 events 
    def canvas_button1_down_event(self,event):
        canvas = event.widget
        x,y = canvas.canvasx(event.x),canvas.canvasy(event.y)
        d = 3
        items = self.getPickBoxItems()
        if items:
            self.select_events_by_canvas_ids(items)
        else:
            # begin select-box operation
            self.beginSelectBox(x,y)
    def canvas_button1_motion_event(self,event):
        canvas = event.widget
        x,y = canvas.canvasx(event.x),canvas.canvasy(event.y)
        self.adjustSelectBox(x,y)
    def canvas_button1_up_event(self,event):
        canvas = event.widget
        x,y = canvas.canvasx(event.x),canvas.canvasy(event.y)
        self.endSelectBox(x,y)
            
    #_______________________
    #   mousewheel event:
    scale=1.0
    def canvas_mousewheel_event(self,event):
        canvas = event.widget
        x,y = self.state['canvasMouse']
        if event.delta > 0:
            scale = 1.1
        else:
            scale = .9
        if scale*self.scale < 1:
            scale = 1.0/self.scale
        self.scale *= scale
        #print scale,self.scale,x,y
        self.mainCanvas.scale('all',x,y,scale,scale)
        if self.scale == 1.0:
            #print 'refresh'
            self.refreshMainCanvas()
        d = 3
        items = canvas.find_overlapping(x-d,y-d,x+d,y+d)
            
    #________________________
    #   Setup canvas bindings:
    #      - motion
    #      - ipod widget (for shuffling through events)
    def setup_canvas_bindings(self):
        self.mainCanvas.bind('<Motion>',self.canvas_motion_event)
        self.mainCanvas.bind('<Button-1>',self.canvas_button1_down_event)
        self.mainCanvas.bind('<B1-Motion>',self.canvas_button1_motion_event)
        self.mainCanvas.bind('<ButtonRelease-1>',self.canvas_button1_up_event)
        self.mainCanvas.bind('<MouseWheel>',self.canvas_mousewheel_event)
        self.mainCanvas.bind('<Enter>',
                             lambda e,c=self.mainCanvas:c.focus_force())
        self.mainCanvas.bind('<Configure>',
                             lambda e,s=self:s.refreshMainCanvas())

    def refreshAllCanvases(self):
        self.refreshMainCanvas()
        self.refreshEventFilterGraphCanvas()
    #_______________________________________________________________
    # InfoFrame methods
    def resetInfoFrame(self):
        self.resetTopProtocolsList()
        self.resetTopTalkersListeners()
        self.resetTopPairsList()

    #_______________________________________________________________
    # Event Filter Graph methods
    def get_efg_dx(self):
        w,h = self.getEventFilterGraphCanvasSize()
        numSlices = self.current['numSlices']
        dx = w/float(numSlices)
        return dx
    def get_efg_slice_info_from_x(self,x):
        w,h = self.getEventFilterGraphCanvasSize()
        numSlices = self.current['numSlices']
        dx = w/float(numSlices)
        slice = int(math.floor(x/dx))
        return {'slice':slice,'dx':dx,'w':w,'h':h}
    def event_filter_graph_motion_event(self,event):
        efgState = self.state['eventFilterGraph']
        efgCanvas = self.eventFilterGraphCanvas
        if efgState['highlightedSlice'] is not None:
            id,sliceNo = efgState['highlightedSlice']
            efgCanvas.delete(id)
        info = self.get_efg_slice_info_from_x(event.x)
        slice,dx,h = (info['slice'],info['dx'],info['h'])
        id = efgCanvas.create_rectangle(slice*dx,0,
                                        (slice+1)*dx,h,
                                        outline='#fbb')
        self.state['eventFilterGraph']['highlightedSlice'] = [id,slice]
    def setEventFilterGraphTimeSlice(self,timeSlice):
        efgState = self.state['eventFilterGraph']
        efgCanvas = self.eventFilterGraphCanvas
        if efgState['selectedSlice'] is not None:
            id,sliceNo = efgState['selectedSlice']
            efgCanvas.delete(id)
        dx = self.get_efg_dx()
        w,h = self.getEventFilterGraphCanvasSize()
        id = efgCanvas.create_rectangle(timeSlice*dx,0,
                                        (timeSlice+1)*dx,h,
                                        outline='red')
        self.state['eventFilterGraph']['selectedSlice'] = [id,slice]
    def event_filter_graph_button1_event(self,event):
        #print 'Event Filter Graph (x: %s,y: %s)'%(event.x,event.y)
#         efgState = self.state['eventFilterGraph']
#         efgCanvas = self.eventFilterGraphCanvas
#         if efgState['selectedSlice'] is not None:
#             id,sliceNo = efgState['selectedSlice']
#             efgCanvas.delete(id)
        info = self.get_efg_slice_info_from_x(event.x)
        slice,dx,h = (info['slice'],info['dx'],info['h'])
        filters = self.state['graph']['locations'].get(slice,[])
        minEventNumber = 1e300
        for filter in filters:
            stats = self.timeline.getFilterStatistics(filter)
            firstEventAbs,firstEventRel = stats['firstEvent'][slice]
            #print 'First event:',firstEventRel
            if  firstEventRel < minEventNumber:
                minEventNumber = firstEventRel
        self.sliceSlider.set(slice+1)
        self.sliceSlider.update_idletasks()
        if minEventNumber != 1e300:
            #print 'Setting fist event',minEventNumber
            self.eventSlider.set(minEventNumber+1)
            #self.set_event(minEventNumber+1)
        self.setEventFilterGraphTimeSlice(slice)
#         id = efgCanvas.create_rectangle(slice*dx,0,
#                                         (slice+1)*dx,h,
#                                         outline='red')
#         self.state['eventFilterGraph']['selectedSlice'] = [id,slice]
    def setupEventFilterGraph(self,parent):
        gFrame = Frame(parent,
                       bd=2,relief='sunken',
                       )
        self.eventFilterGraphCanvas = canvas = Canvas(
            gFrame,height=50,
            bg='white',
            )
        canvas.bind('<Button-1>',self.event_filter_graph_button1_event)
        canvas.bind('<Motion>',self.event_filter_graph_motion_event)
        canvas.bind('<Configure>',
                    lambda e,s=self:s.refreshEventFilterGraphCanvas())
        canvas.pack(fill=BOTH,expand=1)
        return gFrame
    def getEventFilterGraphCanvasSize(self):
        efgCanvas = self.eventFilterGraphCanvas
        return efgCanvas.winfo_width(),efgCanvas.winfo_height()
    def refreshEventFilterGraphCanvas(self):
        if not self.timeline.isInitialised(): return
        # Graph
        filters = self.getEventFilters()
        filterHashes = []
        #print filters
        for filter in filters:
            #print type(filter)
            filterHashes.append(hash(filter))
        efgCanvas = self.eventFilterGraphCanvas
        # remove old filter graphs
        for efHash in self.state['graph'].keys():
            IDs = self.state['graph'][efHash]
            for id in IDs:
                #print 'deleting',id
                efgCanvas.delete(id)
            del self.state['graph'][efHash]
        # redraw filter graphs
        w,h = self.getEventFilterGraphCanvasSize()
        #print 'Event Filter Canvas w:',w,'h:',h
        self.state['graph']['locations'] = locations = {}
        for filter in filters:
            if not filter['graph']: continue
            efHash = hash(filter)
            stats = self.timeline.getFilterStatistics(filter)
            padx=0
            w,h = self.getEventFilterGraphCanvasSize()
            w-=2*padx
            numSlices = self.current['numSlices']
            dx = w/float(numSlices)
            rangeY = h #.04*h
            curX = 0
            for sliceNo in stats['locations']:
                maxEvents = stats['maxEvents']
                y = h-(rangeY*(stats['frequency'][sliceNo]/
                               float(maxEvents)))
                x0,y0,x1,y1 = (padx+sliceNo*dx, h,
                               padx+(sliceNo+1)*dx, y)
                id = efgCanvas.create_rectangle(x0,y0,x1,y1,
                                                fill='grey',
                                                )
                #print id,x0,y0,x1,y1
                if efHash not in self.state['graph']:
                    self.state['graph'][efHash] = [id]
                else:
                    self.state['graph'][efHash].append(id)
                if sliceNo not in locations:
                    locations[sliceNo] = [filter]
                else:
                    locations[sliceNo].append(filter)
    #---------------------------------------------------------------
                

    #________________________
    #   Filter methods
    def refreshProtocols(self):
        self.refreshMainCanvas()
        #self.syncMetadata()
    def tagProtocolAsImportant(self,port):
        displayPorts = self.getImportantProtocolsDisplayInfo()
        portOrder = self.getImportantProtocolsDisplayOrder()
        if port not in displayPorts:
            displayPorts[port] = {}
        #if port not in portOrder:
        #    portOrder.append(port)
            self.refreshProtocols()

    #_______________________________________________________________
    # Event filters
    def addFilter(self,**kw):
        eventFilter = EventFilter(**kw)
        self.current['filter'].append(eventFilter)
        self.refreshMainCanvas()
        self.refreshEventFilterGraphCanvas()
        self.resetEventFilterList()
    def editFilter(self,index,**kw):
        eventFilter = self.current['filter'][index]
        eventFilter.update(kw)
        self.refreshMainCanvas()
        self.refreshEventFilterGraphCanvas()
        self.resetEventFilterList()
    def deleteFilter(self,index):
        self.current['filter'].pop(index)
        self.refreshMainCanvas()
        self.refreshEventFilterGraphCanvas()
        self.resetEventFilterList()
    def getEventFilters(self):
        return self.current['filter']
    def testEvent(self,event):
        # short-circuit OR test
        if not self.getEventFilters(): return True
        testFilters = []
        for filter in self.getEventFilters():
            if filter.isIsolate():
                testFilters.append(filter)
        if not testFilters: return True
        for filter in testFilters:
            if filter.testEvent(event): return True
        return False
    def event_filter_create_button(self):
        pack = self.eventFilterPack
        talker=pack['var']['talker'].get()
        listener=pack['var']['listener'].get()
        port=pack['var']['port'].get()
        bytes=pack['var']['bytes'].get()
        isolate = pack['var']['isolate'].get()
        graph = pack['var']['graph'].get()
        if talker or listener or port or bytes:
            self.addFilter(
                talker=talker,
                listener=listener,
                port=port,
                bytes=bytes,
                isolate=isolate,
                graph=graph,
                )
    def event_filter_edit_button(self):
        filterIndex = self.getSelectedEventFilter()
        if filterIndex is not None:
            pack = self.eventFilterPack
            self.editFilter(
                filterIndex,
                talker=pack['var']['talker'].get(),
                listener=pack['var']['listener'].get(),
                port=pack['var']['port'].get(),
                bytes=pack['var']['bytes'].get(),
                isolate=pack['var']['isolate'].get(),
                graph=pack['var']['graph'].get(),
                )
    def event_filter_delete_button(self):
        filterIndex = self.getSelectedEventFilter()
        if filterIndex is not None:
            self.deleteFilter(filterIndex)
    def getSelectedEventFilter(self):
        listIndexes = self.state['eventFilterList']['indexes']
        self.state['selected']['eventFilter'] = listIndexes
        if listIndexes:
            return listIndexes[0]
    def resetEventFilterList(self):
        print 'Resetting event filter list'
        eventFilters = self.getEventFilters()
        filterList = self.eventFilterList
        filterList.clear()
        for i,eventFilter in enumerate(eventFilters):
            tup = ('.'.join(map(str,eventFilter['talker'])),
                   '.'.join(map(str,eventFilter['listener'])),
                   str(eventFilter['port']),
                   str(eventFilter['bytes']),
                   )
            filterList.insert(END,tuple(tup))
    def event_filter_list_callback(self,listIndexes):
        self.state['eventFilterList']['indexes'] = map(int,listIndexes)
        filters = self.getEventFilters()
        filterIndex = self.getSelectedEventFilter()
        if filterIndex is not None:
            filter = filters[filterIndex]
            port,bytes,talker,listener = (filter['port'],
                                          ''.join(map(str,filter['bytes'])),
                                          filter['talker'],
                                          filter['listener'],
                                          )
        else:
            port,bytes,talker,listener = None,None,None,None
        self.setPortEventFilterInput(port)
        self.setBytesEventFilterInput(bytes)
        self.setTalkerEventFilterInput(talker)
        self.setListenerEventFilterInput(listener)
        self.eventFilterPack['var']['isolate'].set(filter['isolate'])
        self.eventFilterPack['var']['graph'].set(filter.isGraph())
            
    def setupEventFilterInputs(self,parent):
        efFrame = Frame(parent,bd=2,relief='sunken')
        efLabel = Label(efFrame,text='EventFilters')
        efLabel.pack(side=TOP)
        efInputsAndListFrame = Frame(efFrame)
        efInputFrame = Frame(efInputsAndListFrame)
        textVars = [StringVar(),StringVar(),StringVar(),StringVar(),
                    StringVar(),]
        clearTalker=lambda s=self:s.eventFilterPack['var']['talker'].set('')
        clearListener=lambda s=self:s.eventFilterPack['var']['listener'].set('')
        clearPort=lambda s=self:s.eventFilterPack['var']['port'].set('')
        clearBytes=lambda s=self:s.eventFilterPack['var']['bytes'].set('')
        self.eventFilterPack = pack = {
#             'label': {'talker': Label(efInputFrame,text='Talker'),
#                       'listener': Label(efInputFrame,text='Listener'),
#                       'port': Label(efInputFrame,text='Port'),
#                       'bytes': Label(efInputFrame,text='Bytes'),
#                       },
            'label': {'talker': Button(efInputFrame,text='Talker',
                                       command=clearTalker,
                                       ),
                      'listener': Button(efInputFrame,text='Listener',
                                         command=clearListener,
                                         ),
                      'port': Button(efInputFrame,text='Port',
                                     command=clearPort,
                                     ),
                      'bytes': Button(efInputFrame,text='Bytes',
                                      command=clearBytes,
                                      ),
                      },
            'entry': {'talker': Entry(efInputFrame,textvariable=textVars[0],
                                      width=15),
                      'listener': Entry(efInputFrame,textvariable=textVars[1],
                                      width=15),
                      'port': Entry(efInputFrame,textvariable=textVars[2],
                                      width=15),
                      'bytes': Entry(efInputFrame,textvariable=textVars[3],
                                      width=15),
                      },
            'var': {'talker':textVars[0],
                     'listener':textVars[1],
                     'port':textVars[2],
                     'bytes':textVars[3],
                    'isolate':IntVar(),
                    'graph':IntVar(),
                     },
            }
        setup = [
            [pack['label']['talker'],pack['entry']['talker']],
            [pack['label']['listener'],pack['entry']['listener']],
            [pack['label']['port'],pack['entry']['port']],
            [pack['label']['bytes'],pack['entry']['bytes']],
            ]
        for i,pair in enumerate(setup):
            for j,widget in enumerate(pair):
                widget.grid(row=i,column=j,sticky=EW)
        efInputFrame.columnconfigure(1,weight=1)

        buttonFrame = Frame(efInputFrame)
        # Create New
        createButton = Button(
            buttonFrame,
            text='Create',
            command=self.event_filter_create_button,
            )
        #createButton.grid(row=i+1,column=0,columnspan=2,sticky='w')
        createButton.pack(side=LEFT,expand=1,fill='x')
        # Edit
        editButton = Button(
            buttonFrame,
            text='Edit',
            command=self.event_filter_edit_button,
            )
        #editButton.grid(row=i+2,column=0,columnspan=2,sticky='w')
        editButton.pack(side=LEFT,expand=1,fill='x')
        # Delete
        deleteButton = Button(
            buttonFrame,
            text='Delete',
            command=self.event_filter_delete_button,
            )
        #deleteButton.grid(row=i+3,column=0,columnspan=2,sticky='w')
        deleteButton.pack(side=LEFT,expand=1,fill='x')

        buttonFrame.grid(row=i+1,columnspan=2,sticky='ew')

        cbFrame = Frame(efInputFrame)
        isolateLabel = Label(
            cbFrame,
            text='Isolate',
            )
        isolateLabel.pack(side=LEFT)
        isolateCheck = Checkbutton(
            cbFrame,
            variable=pack['var']['isolate'],
            )
        isolateCheck.pack(side=LEFT)
        graphLabel = Label(
            cbFrame,
            text='Graph',
            )
        graphLabel.pack(side=LEFT)
        graphCheck = Checkbutton(
            cbFrame,
            variable=pack['var']['graph'],
            )
        graphCheck.pack(side=LEFT)
        cbFrame.grid(row=i+2,columnspan=2,sticky='ew')
        
        efInputFrame.pack(side=LEFT,fill=X,expand=1,anchor='n')

        mlbFrame = Frame(efInputsAndListFrame)
        headings = (
            ('Talker',8),('Listener',8),('Port',4),('Bytes',3),
            )
        self.eventFilterList = MultiListbox(
            mlbFrame,headings,
            height=5,
            command=self.event_filter_list_callback,
            #label='Event Filters',
            )
        mlbFrame.pack(side=RIGHT,fill=BOTH,expand=1)
        self.resetEventFilterList()

        efInputsAndListFrame.pack(side=BOTTOM,fill=BOTH,expand=1)

        return efFrame

    def setPortEventFilterInput(self,port):
        pack = self.eventFilterPack
        port = port if (port is not None) else ''
        pack['var']['port'].set(str(port))
        if port is not '':
            self.state['eventFilterInput']['port'] = port
        else:
            self.state['eventFilterInput']['port'] = None
    def setBytesEventFilterInput(self,bytes):
        pack = self.eventFilterPack
        bytes = bytes if (bytes is not None) else ''
        pack['var']['bytes'].set(str(bytes))
        if bytes is not '':
            self.state['eventFilterInput']['bytes'] = bytes
        else:
            self.state['eventFilterInput']['bytes'] = None
    def setTalkerEventFilterInput(self,talker):
        pack = self.eventFilterPack
        talkerStr = ''
        if talker:
            talkerStr = '.'.join(map(str,talker))
            self.state['eventFilterInput']['talker'] = talker
        else:
            self.state['eventFilterInput']['talker'] = None
        pack['var']['talker'].set(talkerStr)
    def setListenerEventFilterInput(self,listener):
        pack = self.eventFilterPack
        listenerStr = ''
        if listener:
            listenerStr = '.'.join(map(str,listener))
            self.state['eventFilterInput']['listener'] = listener
        else:
            self.state['eventFilterInput']['listener'] = None
        pack['var']['listener'].set(listenerStr)
    def setPairEventFilterInput(self,pair):
        pack = self.eventFilterPack
        pairStr = ['','']
        if pair:
            pairStr = ['.'.join(map(str,pair[0])),
                       '.'.join(map(str,pair[1])),]
            self.state['eventFilterInput']['talker'] = pair[0]
            self.state['eventFilterInput']['listener'] = pair[1]
        else:
            self.state['eventFilterInput']['talker'] = None
            self.state['eventFilterInput']['listener'] = None
        pack['var']['talker'].set(pairStr[0])
        pack['var']['listener'].set(pairStr[1])
    #---------------------------------------------------------------


    #________________________
    # Top Protocols methods
    def refreshTopProtocolsList(self):
        port = self.getSelectedTopProtocol()
        displayPorts = self.getImportantProtocolsDisplayInfo()
        default = self.getDefaultHttpDisplayInfo()
        color = default['color']
        name = str(port)
        if port in displayPorts:
            name = displayPorts[port].get('name',name)
            color = displayPorts[port].get('color',color)
        #isolate = self.isProtocolIsolated(port)
        #self.topProtocolPack['name'].set(name)
        #self.topProtocolNameTextVar.set(name)
        #self.topProtocolPack['isolate'].set(1 if isolate else 0)
        #self.topProtocolIsolateIntVar.set(1 if isolate else 0)
        #color = self.to_color_string(*color[0])
        # XXX need to do something with this color...
        #self.topProtocolPack['colorButton']['bg'] = color
    def top_protocol_callback(self,listIndexes):
        self.state['topProtocolsList']['indexes'] = listIndexes
        self.setPortEventFilterInput(self.getSelectedTopProtocol())
        self.refreshTopProtocolsList()
    def set_top_protocol_color_button_click(self):
        print 'changing color'
        port = self.getSelectedTopProtocol()
        # if a port is selected
        if port is not None:
            displayPorts = self.getImportantProtocolsDisplayInfo()
            default = self.getDefaultHttpDisplayInfo()
            defaultColor = default['color']
            args = ()
            color = displayPorts.get(port,{}).get('color',None)
            if color:
                color = self.to_color_string(*color[0])
            else:
                color = self.to_color_string(*defaultColor[0])
            args = (color,)
            color = tkColorChooser.askcolor(*args)
            print color
            if color[0] is not None:
                color = list(color[0])
                darkColor = [0,0,0]
                # tkColorChooser gives tuple of integers [0-255], so need
                # to normalize 
                for i,c in enumerate(color):
                    color[i]/=255.
                    darkColor[i] = color[i]#*.75
                color = [darkColor,defaultColor[1],color]
                self.setImportantProtocolColor(port,color)
            self.refreshProtocols()
            self.resetTopProtocolsList()
    def getSelectedTopProtocol(self):
        selectedPorts = []
        listIndexes = self.state['topProtocolsList']['indexes']
        for index in listIndexes:
            port = self.topProtocolHash[int(index)]
            selectedPorts.append(port)
        self.state['selected']['topProtocols'] = selectedPorts
        if selectedPorts:
            return selectedPorts[0]
    def setupTopProtocols(self,parent):
        # Top protocols
        pFrame = Frame(parent,bd=2,relief='sunken')
        mlbFrame = Frame(pFrame)
        headings = (
            ('Protocol',6),('Percent',5),('#',5),
            )
        self.topProtocolsList = protoList = MultiListbox(
            mlbFrame,headings,
            command = self.top_protocol_callback,
            label='Top Protocols',
            height=5,
            )
        protoList.pack(fill=BOTH,expand=1)
        mlbFrame.pack(fill=BOTH,expand=1)
        self.resetTopProtocolsList()

        # individually selected protocol

        return pFrame

    protocolHash = None
    def resetTopProtocolsList(self):
        protoList = self.topProtocolsList
        protoList.clear()

        if not self.timeline.isInitialised(): return

        stats = self.timeline['stats']
        ports, portsSorted = (self.timeline['ports'],
                              self.timeline['portsSorted'])
        top20 = portsSorted[-20:]; top20.reverse()
        displayPorts = self.getImportantProtocolsDisplayInfo()
        # HTTP first, assuming it's in the top 20
        tup = ('HTTP',
               '%.2f'%(100*ports[80]['percent']),
               ports[80]['num'])
        self.topProtocolHash = [80]
        protoList.insert(END,tup)
        top20.remove(80)
        # print default protocols
        portOrder = self.getImportantProtocolsDisplayOrder()
        for port in portOrder:
            percent,num = 0,0
            if port in ports:
                percent = ports[port]['percent']
                num = ports[port]['num']
            portString = displayPorts[port].get('name',str(port))
            tup = (portString,
                   '%.2f'%(100*percent),
                   str(num))
            self.topProtocolHash.append(port)
            protoList.insert(END,tup)
            if port in top20:
                top20.remove(port)
        # print top 20
        for port in top20:
            if port in displayPorts:
                portString = displayPorts[port].get('name',str(port))
            else:
                portString = str(port)
            tup = (portString,
                   '%.2f'%(100*ports[port]['percent']),
                   str(ports[port]['num']))
            self.topProtocolHash.append(port)
            protoList.insert(END,tup)
    #------------------------

    #________________________
    # Top IP Pairs methods
    def setTopPairsListIndexes(self,listIndexes):
        self.state['topPairsList']['indexes'] = listIndexes
    def getSelectedTopPair(self):
        selectedPairs = []
        listIndexes = self.state['topPairsList']['indexes']
        for index in listIndexes:
            pair = self.topPairHash[int(index)]
            selectedPairs.append(pair)
        self.state['selected']['topPairs'] = selectedPairs
        if selectedPairs:
            return selectedPairs[0]
    def top_pair_callback(self,listIndexes):
        self.setTopPairsListIndexes(listIndexes)
        self.setPairEventFilterInput(self.getSelectedTopPair())
    def resetTopPairsList(self):
        pairList = self.topPairsList
        pairList.clear()

        if not self.timeline.isInitialised(): return 

        stats = self.timeline['stats']
        pairs, pairsSorted = (self.timeline['pairs'],
                              self.timeline['pairsSorted'])
        top20 = pairsSorted[-20:]; top20.reverse()
        # print top 20
        self.topPairHash = []
        for pair in top20:
            tIndex,lIndex = pair
            talker,listener = (self.timeline.getTalker(tIndex),
                               self.timeline.getListener(lIndex))
            self.topPairHash.append([talker,listener])
            tup = ('.'.join(map(str,talker)),
                   '.'.join(map(str,listener)),
                   str(len(pairs[pair]['list'])),
                   str(pairs[pair]['bytes']),
                   )
            pairList.insert(END,tup)
    def setupTopPairs(self,parent):
        # Top protocols
        pFrame = Frame(parent,bd=2,relief='sunken')
        mlbFrame = Frame(pFrame)
        headings = (
            ('Talker',12),('Listener',12),('#',5),('Bytes',6),
            )
        self.topPairsList = pairList = MultiListbox(
            mlbFrame,headings,
            command = self.top_pair_callback,
            label='Top Pairs',
            height=5,
            )
        pairList.pack(fill=BOTH,expand=1)
        mlbFrame.pack(fill=BOTH,expand=1)
        self.resetTopPairsList()

        # individually selected pair
#         self.topPairsPack = self.get_mod_pack(
#             pFrame,
#             self.set_top_protocol_isolate_checkbutton,
#             self.set_top_protocol_name_entry,
#             self.set_top_protocol_color_button_click,
#             )

        return pFrame
    #------------------------

    #________________________
    # Top Talkers, Listeners methods
    def getSelectedTopTalker(self):
        selectedIps = []
        listIndexes = self.state['topTalkersList']['indexes']
        for index in listIndexes:
            ip = self.topTalkerHash[int(index)]
            selectedIps.append(ip)
        self.state['selected']['topTalkers'] = selectedIps
        if selectedIps:
            return selectedIps[0]
    def getSelectedTopListener(self):
        selectedIps = []
        listIndexes = self.state['topListenersList']['indexes']
        for index in listIndexes:
            ip = self.topListenerHash[int(index)]
            selectedIps.append(ip)
        self.state['selected']['topListeners'] = selectedIps
        if selectedIps:
            return selectedIps[0]
    def top_talker_callback(self,listIndexes):
        self.state['topTalkersList']['indexes'] = listIndexes
        self.setTalkerEventFilterInput(self.getSelectedTopTalker())
        #print listIndexes
    def top_listener_callback(self,listIndexes):
        self.state['topListenersList']['indexes'] = listIndexes
        self.setListenerEventFilterInput(self.getSelectedTopListener())
        #print listIndexes
    def setupTopTalkersListeners(self,parent):
        tlFrame = Frame(parent,bd=2,relief='sunken')
        talkerFrame = Frame(tlFrame,bd=1,relief='sunken')
        headings = (
            ('IP',14),
            ('#',4),
            )
        self.topTalkersList = talkerList = MultiListbox(
            talkerFrame,headings,
            command = self.top_talker_callback,
            label='Top Talkers',
            height=5,
            )
        talkerFrame.pack(side=LEFT,fill=X,expand=1)
        listenerFrame = Frame(tlFrame,bd=1,relief='sunken')
        headings = (
            ('IP',14),
            ('#',4),
            )
        self.topListenersList = listenerList = MultiListbox(
            listenerFrame,headings,
            command = self.top_listener_callback,
            label='Top Listeners',
            height=5,
            )
        listenerFrame.pack(side=LEFT,fill=X,expand=1)
        self.resetTopTalkersListeners()

        #self.talkerPack = self.get_mod_pack(talkerFrame,None,None,None)
        #self.listenerPack = self.get_mod_pack(listenerFrame,None,None,None)

        return tlFrame

    def resetTopTalkersListeners(self):
        talkerList = self.topTalkersList
        listenerList = self.topListenersList
        talkerList.clear();listenerList.clear()

        if not self.timeline.isInitialised(): return
        
        talkers,talkersSorted = (self.timeline['talkers'],
                                 self.timeline['talkersSorted'],)
        listeners,listenersSorted = (self.timeline['listeners'],
                                 self.timeline['listenersSorted'],)
        top20talkers,top20listeners = (talkersSorted[-20:],
                                       listenersSorted[-20:],)
        top20talkers.reverse()
        top20listeners.reverse()
        self.topTalkerHash,self.topListenerHash = [],[]
        for talker in top20talkers:
            talkerString = '.'.join(map(str,talker))
            talkerList.insert(END,(talkerString,str(talkers[talker]['num'])))
            self.topTalkerHash.append(talker)
        for listener in top20listeners:
            listenerString = '.'.join(map(str,listener))
            listenerList.insert(END,(listenerString,
                                     str(listeners[listener]['num'])))
            self.topListenerHash.append(listener)
    #---------------------------------------------------------------


    #________________________
    #   Main setup method
    def SetupGUI(self):
        w,h = self.initWidth,self.initHeight
        timeSlices = self.current['numSlices']

        animationFrame = Frame(self)

        self.mainCanvas = Canvas(animationFrame,
                                 width=w,height=h,
                                 bd=2,relief='raised',
                                 bg='white',
                                 )
        self.mainCanvas.grid(row=0,column=0,sticky=N+E+W+S)
        self.setup_canvas_bindings()

        # sliders
        self.eventSlider = Scale(animationFrame,
                                 from_=1,to=2,
                                 command=self.set_event,
                                 orient=HORIZONTAL,
                                 )
        self.eventSlider.grid(row=1,column=0,sticky=E+W)
        self.sliceSlider = Scale(animationFrame,
                                     from_=1,to=timeSlices,
                                     command=self.set_time_slice,
                                     orient=HORIZONTAL,
                                     )
        self.sliceSlider.grid(row=2,column=0,sticky=E+W)
        animationFrame.columnconfigure(0,weight=1)
        animationFrame.rowconfigure(0,weight=1)
        

        self.infoFrame = infoFrame = Frame(self,
                                           padx=2,pady=2,
                                           bd=2,relief='raised',
                                           #orient=VERTICAL,
                                           )
        menuBar = Menu(self.winfo_toplevel())
        fileMenu = Menu(menuBar,tearoff=0)
        fileMenu.add_command(label='Open',
                             command=self.getNewFile)
        fileMenu.add_command(label='Save Metadata',
                             command=self.syncMetadata)
        menuBar.add_cascade(label="File",menu=fileMenu)

        self.winfo_toplevel().config(menu=menuBar)

#         bFrame = Frame(infoFrame,bd=2,relief='sunken')
#         getNewButton = Button(bFrame,text='Get New PenReg File',
#                               command=self.getNewFile,
#                               )
#         getNewButton.pack()
#         bFrame.pack(side=TOP,fill=X)

        protoPairFrame = Frame(infoFrame)
        #---------------------------
        # Top protocols
        protoFrame = self.setupTopProtocols(protoPairFrame)
        protoFrame.pack(side=LEFT,fill=X,expand=1)

        #---------------------------
        # Top pairs
        pairFrame = self.setupTopPairs(protoPairFrame)
        pairFrame.pack(side=RIGHT,fill=X,expand=1)

        protoPairFrame.pack(side=TOP,fill=X)

        #---------------------------
        # Top talkers and listeners
        tlFrame = self.setupTopTalkersListeners(infoFrame)
        tlFrame.pack(side=TOP,fill=X)

        #---------------------------
        # Event filters
        efFrame = self.setupEventFilterInputs(infoFrame)
        efFrame.pack(side=TOP,fill=BOTH,expand=1)

        #---------------------------
        # Filter Graph
        graphFrame = self.setupEventFilterGraph(infoFrame)
        graphFrame.pack(side=TOP,fill=X,expand=1)

        #---------------------------
        # Selected events
        slFrame = self.setupSelectList(infoFrame)
        slFrame.pack(side=TOP,expand=1,fill=BOTH)

        
        self.add(animationFrame)
        self.add(infoFrame)
        self.winfo_toplevel().rowconfigure(0,weight=1)
        self.winfo_toplevel().columnconfigure(0,weight=1)
        self.winfo_toplevel().columnconfigure(1,weight=1)
        self.winfo_toplevel().title(self.current['fileName'])
        self.grid(sticky=N+E+W+S)

        self.guiInitialized = True
    #---------------------------------------------------------------
    #---------------------------------------------------------------


if __name__=='__main__':
    root = Tk()
    ptk = NetviewTk(root)
    #sys.exit(0)
    ptk.pack(expand=1,fill='both')
    ptk.focus_force()
    root.bind('<Control-q>',lambda e,s=sys:s.exit(0))
    root.mainloop()
